package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/thoas/go-funk"
)

var (
	confirm    = flag.Bool("y", false, "confirm delete")
	configPath = flag.String("c", "", "absolute config path")
	relatePath = flag.String("r", "", "relate dir path base on path in config.json file")
)

var (
	config             *Config
	ImageReg           = regexp.MustCompile(`[^!\[\]()]+\.(png|jpe?g|gif|svg)`)                    // table.png
	ImageBlockReg      = regexp.MustCompile(`!\[[^!\[\]()]+]\([^!\[\]()]+\.(png|jpe?g|gif|svg)\)`) // ![abc](table.png)
	ImageRelatePathReg = regexp.MustCompile(`\([^!\[\]()]+\.(png|jpe?g|gif|svg)\)`)                // (table.png)
	WXImageReg         = regexp.MustCompile(`!\[图片]\([^(png|jpe?g|gif|svg)]+\)`)                   // ![图片](640-20210911135652417)
	c                  = 0                                                                         // image count
)

func Init() {
	if err := flag.CommandLine.Parse(os.Args[2:]); err != nil {
		panic(err)
	}
	config = getConfig(*configPath)
}

func main() {
	Init()
	path := config.Path
	if *relatePath != "" {
		path = filepath.Join(config.Path, *relatePath)
		fmt.Println(path)
	}

	switch os.Args[1] {
	case "config":
		fmt.Printf("%+v\n", config)
	case "rm":
		DeleteUnusedImage(path)
	case "print":
		HandleAllMDFileInDir(path, PrintImageBlock)
		fmt.Println(c)
	case "wx":
		HandleAllMDFileInDir(path, WXImageHandler)
		fmt.Println(c)
	case "push":
		Push(config.Path)
	case "2zhihu":
		Push(config.Path)
		MD2Zhihu(path)
	default:
		fmt.Println("not support action")
	}
}

func DeleteUnusedImage(path string) {
	usedImagePath := make(map[string]struct{})
	allImagePath := make(map[string]struct{})
	collectImage(path, allImagePath, usedImagePath)
	shouldDeleteImagePath, NotExistedImagePath := funk.DifferenceString(funk.Keys(allImagePath).([]string), funk.Keys(usedImagePath).([]string))
	fmt.Println("*\t*\t*\t should delete image")
	for _, s := range shouldDeleteImagePath {
		fmt.Println(s)
		if *confirm {
			if err := os.Remove(s); err != nil {
				panic(err)
			}
			fmt.Println("*\t*\t*\t delete success")
		}
	}

	fmt.Println("*\t*\t*\t not existed image")
	for _, s := range NotExistedImagePath {
		fmt.Println(s)
	}
}

func collectImage(path string, allImagePath, usedImagePath map[string]struct{}) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		panic(err)
	}
	dirs := make([]string, 0)
	for _, file := range files {
		//fmt.Println(file.IsDir(), file.Name())
		switch {
		case file.IsDir() && !funk.ContainsString(config.Ignore, file.Name()):
			dirs = append(dirs, file.Name())
		case IsImageFile(file.Name()):
			allImagePath[path+"/"+file.Name()] = struct{}{}
		case IsMD(file.Name()):
			data, err := ioutil.ReadFile(path + "/" + file.Name())
			if err != nil {
				panic(err)
			}
			for _, b := range ImageBlockReg.FindAll(data, -1) {
				imageName := GetImagePathFromBlock(b)
				usedImagePath[path+"/"+string(imageName)] = struct{}{}
			}
		}
	}

	for _, d := range dirs {
		collectImage(path+"/"+d, allImagePath, usedImagePath)
	}
}

func HandleAllMDFileInDir(path string, mdHandler func(path string, filename string)) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		panic(err)
	}
	dirs := make([]string, 0)
	for _, file := range files {
		if file.IsDir() {
			dirs = append(dirs, file.Name())
			continue
		}
		if !file.IsDir() && IsMD(file.Name()) {
			fmt.Println(file.Name())
			mdHandler(path, file.Name())
		}
	}

	for _, d := range dirs {
		HandleAllMDFileInDir(path+"/"+d, mdHandler)
	}
}

func PrintImageBlock(path, filename string) {
	data, err := ioutil.ReadFile(path + "/" + filename)
	if err != nil {
		panic(err)
	}
	for _, b := range ImageBlockReg.FindAll(data, -1) {
		fmt.Printf("\t%s %s\n", b, GetImagePathFromBlock(b))
		c++
	}
}

func WXImageHandler(path, filename string) {
	data, err := ioutil.ReadFile(path + "/" + filename)
	if err != nil {
		panic(err)
	}
	wxImages := WXImageReg.FindAll(data, -1)
	imageNames := make([]string, len(wxImages))
	for i, b := range wxImages {
		imageNames[i] = string(b[bytes.IndexByte(b, '(')+1 : len(b)-1])
		fmt.Printf("\t%s %s\n", b, imageNames[i])
		c++
	}

	if *confirm {
		// 首先确认一下所有的文件都在
		for _, s := range imageNames {
			if !IsExists(path + "/" + s) {
				panic(fmt.Sprintf("not found file %s", path+"/"+s))
			}
		}

		// rename & edit markdown
		for i, s := range imageNames {
			if err := os.Rename(path+"/"+s, path+"/"+s+".png"); err != nil {
				panic(err)
			}
			data = bytes.ReplaceAll(data, wxImages[i], []byte(fmt.Sprintf("![图片](%s.png)", s)))
		}
		if err := ioutil.WriteFile(path+"/"+filename, data, 0); err != nil {
			panic(err)
		}
	}
}

func runCMD(path, name string, arg ...string) error {
	c := exec.Command(name, arg...)
	c.Dir = path
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	return c.Run()
}

func Push(path string) {
	if err := runCMD(path, "git", "status", "."); err != nil {
		panic(err)
	}

	if err := runCMD(path, "git", "add", "."); err != nil {
		panic(err)
	}

	if err := runCMD(path, "git", "commit", "-a", "-m", "add"); err != nil {
		return
	}

	if err := runCMD(path, "git", "push"); err != nil {
		panic(err)
	}
}

// MD2Zhihu get path = "/Users/bytedance/微云同步助手(1185671574)/笔记", file = "健身/动作.md"
func MD2Zhihu(absPath string) {
	f, err := os.Open(absPath)
	if err != nil {
		panic(err)
	}

	if !IsMD(f.Name()) {
		fmt.Println("please point out the .md file that you want to transact")
		return
	}

	regPath := strings.Replace(filepath.Dir(absPath), config.Path+`/`, "", 1) // regPath = "健身"
	data, err := ioutil.ReadAll(f)
	if err != nil {
		panic(err)
	}

	data = replaceImageSource(regPath, config.GitRepoUrl, data)
	if err := os.MkdirAll(config.ZhihuOut, os.ModePerm); err != nil {
		panic(err)
	}
	t := filepath.Join(config.ZhihuOut, "temp.md")
	if err := ioutil.WriteFile(t, data, os.ModePerm); err != nil {
		panic(err)
	}
	if err := runCMD(filepath.Dir(absPath), "md2zhihu", t, "-o", filepath.Join(config.ZhihuOut, filepath.Base(absPath))); err != nil {
		panic(err)
	}
	if err := os.Remove(t); err != nil {
		panic(err)
	}
}

func replaceImageSource(regPath, url string, data []byte) []byte {
	imageBlockS := ImageBlockReg.FindAll(data, -1)
	if imageBlockS == nil || len(imageBlockS) == 0 {
		fmt.Println("!no image found in .md")
		return data
	}

	replace := make(map[string]string)
	for _, imageBlock := range imageBlockS { // ![abc](table.png)
		imagePath := GetImagePathFromBlock(imageBlock) // table.png
		replace[string(imageBlock)] = `<img src="` + strings.Join([]string{url, regPath, string(imagePath)}, `/`) +`">`
	}

	for key, value := range replace {
		data = bytes.Replace(data, []byte(key), []byte(value), -1)
	}
	return data
}

func IsMD(name string) bool {
	if name == ".md" {
		return false
	}
	return name[len(name)-3:] == ".md"
}

func IsImageFile(name string) bool {
	result := ImageReg.FindAllString(name, -1)
	return result != nil && result[0] == name
}

func GetImagePathFromBlock(block []byte) []byte {
	result := ImageRelatePathReg.FindAll(block, -1)
	if len(result) != 1 {
		panic(result)
	}

	return result[0][1 : len(result[0])-1]
}

func IsExists(path string) bool {
	_, err := os.Stat(path) //os.Stat获取文件信息
	if err != nil {
		return os.IsExist(err)
	}
	return true
}
